/*
 * Copyright (C) 2010 CODEBISTRO LLC, Sunnyvale, California.
 * All rights reserved. Proprietary and Confidential.
 */
package org.codebistro.util;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;

import java.util.Map;
import java.util.HashMap;

import com.google.common.base.Throwables;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

// TODO find appropriate superclass
// TODO handle messages via resources

public class Bark extends RuntimeException {
    private static final long serialVersionUID = -3311292358078826511L;

    static Logger logger = LoggerFactory.getLogger(Bark.class);

    boolean hasInfo = true;        // This exception has info to  display

    public Bark(String reason, Object arg1) {
        this(reason, new Object[]{arg1});
    }

    public Bark(String reason, Object[] args) {
        super(reason + argString(args));
        hasInfo = (reason != null && reason.length() > 0) || !argsEmpty(args);
        log(this);
    }

    public Bark(Throwable cause, Object arg1) {
        this(cause, new Object[]{arg1});
    }

    public Bark(Throwable cause) {
        this(cause, new Object[]{});
    }

    public Bark(Throwable cause, Object[] args) {
        super(argString(args), cause);
        hasInfo = argsEmpty(args);
        log(this);
    }

    public Bark(Object arg1) {
        this(new Object[]{arg1});
    }

    public Bark() {
        this(new Object[]{});
    }

    private Bark(Object[] args) {
        super(argString(args));
        hasInfo = args != null && args.length > 0;
        log(this);
    }

    private static String argString(Object[] args) {
        if (argsEmpty(args))
            return "";
        return "(" + Strings14.join(args, ", ") + ")";
    }

    private static boolean argsEmpty(Object[] args) {
        return args == null || args.length == 0;
    }

    private static boolean isEmpty(String s) {
        return s == null || s.length() == 0;
    }

    /**
     * Make myself transparent if I have nothing to say
     */
    public void printStackTrace(PrintStream out) {
        if (!hasInfo && getCause() != null)
            getCause().printStackTrace(out);
        else
            super.printStackTrace(out);
    }

    /**
     * Make myself transparent if I have nothing to say
     */
    public void printStackTrace(PrintWriter out) {
        if (!hasInfo && getCause() != null)
            getCause().printStackTrace(out);
        else
            super.printStackTrace(out);
    }

    /**
     * Some frequently used barks.
     *
     * @note this is just a conveniency, not a replacement for exception types.
     * Please use customized exception types where appropriate.
     */
    static public Bark notImplementedYet() {
        return new Bark("notImplementedYet");
    }

    public static UnsupportedOperationException unsupported(String message) {
        if (!Strings14.isEmpty(message)) {
            return new UnsupportedOperationException(message);
        }

        return new UnsupportedOperationException();
    }

    public static UnsupportedOperationException unsupported() {
        return unsupported(null);
    }

    static public RuntimeException unchecker(Throwable ex) {
        final Throwable rootCause = Throwables.getRootCause(ex);
        if (rootCause instanceof RuntimeException) {
            return (RuntimeException) rootCause;
        }

        if (ex instanceof RuntimeException) {
            return (RuntimeException) ex;
        }

        return new Bark(ex);
    }

    static boolean enableLogging = false;    // We want exceptions to be logged upon catch

    static void log(Bark who) {
        if (enableLogging) {
            StringWriter writer = new StringWriter();
            who.printStackTrace(new PrintWriter(writer));
            logger.error(who + writer.toString());
        }
    }

    public static String mustImplement(final Class expectedClass, final Class actualClass) {
        return actualClass.getName() + " must implement " + expectedClass.getName();
    }

    public static IllegalStateException invalidState(String message) {
        return new IllegalStateException(message);
    }

    public static IllegalArgumentException invalidValue(String property, Object expected, Object actual) {
        final String msg =
                "invalid " + property + ", " +
                        "expected '" + String.valueOf(expected) + "'," +
                        " actual: '" + String.valueOf(actual) + "'";

        return new IllegalArgumentException(msg);
    }

    public static IllegalArgumentException expectingNotEmpty(String property, Object actual) {
        final String msg = "expecting non-empty " + property + " actual: '" + String.valueOf(actual) + "'";

        return new IllegalArgumentException(msg);
    }

    public static IllegalArgumentException unexpected(String property, Object actual) {
        final String msg = "unexpected " + property + " actual: '" + String.valueOf(actual) + "'";

        return new IllegalArgumentException(msg);
    }

    public static String report(final Throwable throwable) {
        if (throwable == null) {
            return "None";
        }

        final Throwable cause = Throwables.getRootCause(throwable);
        return cause.getClass().getName() + "{" + cause.getMessage() + "}";
    }

    public static Error unreachable(final Throwable cause) {
        return new Error("This error state was concidered unreachable before", cause);
    }

    public static AssertionError unreachable(final String message) {
        return new AssertionError("This code was unreachable before: " + String.valueOf(message));
    }

    /**
     * Do best effort in retrieving meaningful message
     */
    public String getMessage() {
        String message = super.getMessage();
        if (Strings14.isEmpty(message)) {
            Throwable cause = getCause();
            if (cause != null) {
                message = cause.getMessage();
                if (Strings14.isEmpty(message))
                    return cause.getClass().getName();
                else
                    return message;
            }
        }
        return message;
    }

    //	from string to long (last message millis);
    protected final static Map verboseReports = new HashMap();
    protected final static long VERBOSE_TIMEOUT = 10 * 60 * 1000;

    public static Throwable shieldVerbose(Throwable t) {
        if (t == null) {
            return null;
        }
        if (t.getMessage() != null && t.getMessage().toLowerCase().indexOf("license") >= 0) {
            return null;    //      don't report any license-related traces
        }

        final String report = Bark.report(t);

        synchronized (verboseReports) {
            long now = System.currentTimeMillis();
            Long lastReport = (Long) verboseReports.get(report);
            //	FIXME possible memory leak here
            if (lastReport != null && now - lastReport.longValue() < VERBOSE_TIMEOUT) {
                return null;
            }

            verboseReports.put(report, new Long(now));
        }

        return t;
        }
}